package cn.mini.mybatis.mybatis.builder;

import java.util.HashMap;

/**
 * 参数表达式类，继承自 HashMap，用于解析参数表达式并存储解析结果。
 * 可以参考 ParameterExpressionTest 类进行功能测试。
 */
public class ParameterExpression extends HashMap<String, String> {

  // 序列化版本号，确保序列化和反序列化过程中类的版本一致性
  private static final long serialVersionUID = -2417552199605158680L;

  /**
   * 构造函数，接收一个参数表达式字符串并进行解析。
   *
   * @param expression 参数表达式字符串，例如 #{property,javaType=int,jdbcType=NUMERIC}
   */
  public ParameterExpression(String expression) {
    parse(expression);
  }

  /**
   * 解析参数表达式字符串。
   *
   * @param expression 参数表达式字符串
   */
  private void parse(String expression) {
    // 示例：#{property,javaType=int,jdbcType=NUMERIC}
    // 首先去除空白字符，返回第一个非空白字符的位置
    int p = skipWS(expression, 0);
    if (expression.charAt(p) == '(') {
      // 若以 '(' 开头，处理表达式
      expression(expression, p + 1);
    } else {
      // 否则，处理属性
      property(expression, p);
    }
  }

  /**
   * 处理表达式类型的参数。表达式可能是 3.2 的新功能，当前可先忽略其具体业务含义。
   *
   * @param expression 参数表达式字符串
   * @param left 表达式左括号后的起始位置
   */
  // 表达式可能是3.2的新功能，可以先不管
  private void expression(String expression, int left) {
    // 用于匹配括号的计数器，初始值为 1 表示已有一个左括号
    int match = 1;
    // 从左括号后的下一个位置开始遍历
    int right = left + 1;
    // 当 match 大于 0 时，说明还有未匹配的左括号
    while (match > 0) {
      if (expression.charAt(right) == ')') {
        // 遇到右括号，计数器减 1
        match--;
      } else if (expression.charAt(right) == '(') {
        // 遇到左括号，计数器加 1
        match++;
      }
      right++;
    }
    // 将括号内的表达式作为 "expression" 键的值存入 Map
    put("expression", expression.substring(left, right - 1));
    // 继续处理 JDBC 类型相关选项
    jdbcTypeOpt(expression, right);
  }

  /**
   * 处理属性类型的参数。
   *
   * @param expression 参数表达式字符串
   * @param left 属性起始位置
   */
  private void property(String expression, int left) {
    // 示例：#{property,javaType=int,jdbcType=NUMERIC} 或 property:VARCHAR
    if (left < expression.length()) {
      // 首先，找到逗号或者冒号之前的字符串，将其作为属性名
      int right = skipUntil(expression, left, ",:");
      // 将属性名作为 "property" 键的值存入 Map
      put("property", trimmedStr(expression, left, right));
      // 第二，处理 javaType、jdbcType 等选项
      jdbcTypeOpt(expression, right);
    }
  }

  /**
   * 跳过字符串中的空白字符，返回第一个非空白字符的位置。
   *
   * @param expression 输入的字符串
   * @param p 起始位置
   * @return 第一个非空白字符的位置
   */
  private int skipWS(String expression, int p) {
    for (int i = p; i < expression.length(); i++) {
      if (expression.charAt(i) > 0x20) {
        // 0x20 是空格字符的 ASCII 码，大于该值表示非空白字符
        return i;
      }
    }
    return expression.length();
  }

  /**
   * 跳过字符串，直到遇到指定字符集中的字符，返回该字符的位置。
   *
   * @param expression 输入的字符串
   * @param p 起始位置
   * @param endChars 指定的字符集
   * @return 遇到指定字符的位置
   */
  private int skipUntil(String expression, int p, final String endChars) {
    for (int i = p; i < expression.length(); i++) {
      char c = expression.charAt(i);
      if (endChars.indexOf(c) > -1) {
        // 若字符在指定字符集中，返回该字符位置
        return i;
      }
    }
    return expression.length();
  }

  /**
   * 处理 JDBC 类型相关选项。
   *
   * @param expression 参数表达式字符串
   * @param p 起始位置
   */
  private void jdbcTypeOpt(String expression, int p) {
    // 示例：#{property,javaType=int,jdbcType=NUMERIC} 或 property:VARCHAR
    // 首先去除空白字符，返回第一个非空白字符的位置
    p = skipWS(expression, p);
    if (p < expression.length()) {
      // 第一个 property 解析完后可能遇到逗号或冒号
      if (expression.charAt(p) == ':') {
        // 若遇到冒号，处理 JDBC 类型
        jdbcType(expression, p + 1);
      } else if (expression.charAt(p) == ',') {
        // 若遇到逗号，处理其他选项
        option(expression, p + 1);
      } else {
        // 若不是冒号或逗号，抛出解析异常
        throw new RuntimeException("Parsing error in {" + expression + "} in position " + p);
      }
    }
  }

  /**
   * 处理 JDBC 类型。
   *
   * @param expression 参数表达式字符串
   * @param p 起始位置
   */
  private void jdbcType(String expression, int p) {
    // 示例：property:VARCHAR
    // 跳过空白字符，找到 JDBC 类型的起始位置
    int left = skipWS(expression, p);
    // 找到逗号之前的位置，即 JDBC 类型的结束位置
    int right = skipUntil(expression, left, ",");
    if (right > left) {
      // 将 JDBC 类型作为 "jdbcType" 键的值存入 Map
      put("jdbcType", trimmedStr(expression, left, right));
    } else {
      // 若未找到有效的 JDBC 类型，抛出解析异常
      throw new RuntimeException("Parsing error in {" + expression + "} in position " + p);
    }
    // 继续处理其他选项
    option(expression, right + 1);
  }

  /**
   * 递归处理参数表达式中的其他选项。
   *
   * @param expression 参数表达式字符串
   * @param p 起始位置
   */
  private void option(String expression, int p) {
    // 示例：#{property,javaType=int,jdbcType=NUMERIC}
    // 跳过空白字符，找到选项的起始位置
    int left = skipWS(expression, p);
    if (left < expression.length()) {
      // 找到等号之前的位置，即选项名的结束位置
      int right = skipUntil(expression, left, "=");
      // 获取选项名
      String name = trimmedStr(expression, left, right);
      // 等号后的位置作为选项值的起始位置
      left = right + 1;
      // 找到逗号之前的位置，即选项值的结束位置
      right = skipUntil(expression, left, ",");
      // 获取选项值
      String value = trimmedStr(expression, left, right);
      // 将选项名和选项值存入 Map
      put(name, value);
      // 递归调用 option 方法，继续处理逗号后面的属性
      option(expression, right + 1);
    }
  }

  /**
   * 去除字符串首尾的空白字符。
   *
   * @param str 输入的字符串
   * @param start 起始位置
   * @param end 结束位置
   * @return 去除首尾空白字符后的字符串
   */
  private String trimmedStr(String str, int start, int end) {
    // 去除字符串头部的空白字符
    while (str.charAt(start) <= 0x20) {
      start++;
    }
    // 去除字符串尾部的空白字符
    while (str.charAt(end - 1) <= 0x20) {
      end--;
    }
    // 若起始位置大于等于结束位置，返回空字符串，否则返回截取后的字符串
    return start >= end ? "" : str.substring(start, end);
  }

}